package org.hawk.gwt.ppc.reflect;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.hawk.gwt.ppc.Configuration;
import org.hawk.gwt.ppc.GWTModule;
import org.hawk.gwt.ppc.Log;
import org.hawk.gwt.ppc.javacc.JavaParserTreeConstants;
import org.hawk.gwt.ppc.javacc.JavaScanner;
import org.hawk.gwt.ppc.javacc.ParseException;
import org.hawk.gwt.ppc.reflect.lang.ClassDelegate;
import org.hawk.gwt.ppc.reflect.lang.ClassLoaderDelegate;
import org.hawk.gwt.ppc.scanner.ProcessingUnit;
import org.hawk.gwt.ppc.utils.IO;
import org.hawk.gwt.ppc.utils.Name;
import org.hawk.gwt.ppc.utils.Pattern;

/**
 * Facade for all operations necessary for reflection-related preprocessing
 * work.
 * 
 * @author alex.bereznevatiy@gmail.com
 */
public class ReflectionProcessingUnit implements ProcessingUnit {
	private static final String CLASS_PROPERTY = "class";
	private static final String CLASS_DELEGATE_PROPERTY = "$class";
	private MetadataProcessor metadataProcessor;
	private ReflectionCallsProcessor reflectionCallsProcessor;
	private List<Pattern> pattern;

	public ReflectionProcessingUnit() {
		metadataProcessor = new MetadataProcessor();
		reflectionCallsProcessor = new ReflectionCallsProcessor();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.hawk.gwt.ppc.scanner.Preprocessor#configure(org.hawk.gwt.ppc.
	 * Configuration, org.hawk.gwt.ppc.javacc.JavaScanner)
	 */
	public void configure(Configuration config, JavaScanner scanner) {
		configurePattern(config);
		configureMetadataProcessor(scanner);
		configureReflectionCallsProcessor(scanner);
	}

	private void configureReflectionCallsProcessor(JavaScanner scanner) {
		scanner.addProcessor(JavaParserTreeConstants.JJTPRIMARYSUFFIX,
				reflectionCallsProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTNAME,
				reflectionCallsProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTFIELDDECLARATION,
				reflectionCallsProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTTYPE,
				reflectionCallsProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTPRIMARYEXPRESSION,
				reflectionCallsProcessor);
	}

	private void configureMetadataProcessor(JavaScanner scanner) {
		scanner.addProcessor(JavaParserTreeConstants.JJTCOMPILATIONUNIT,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTCLASSORINTERFACEBODY,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTEXTENDSLIST,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTPACKAGEDECLARATION,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTIMPLEMENTSLIST,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTFIELDDECLARATION,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTCONSTRUCTORDECLARATION,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTMETHODDECLARATOR,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTIMPORTDECLARATION,
				metadataProcessor);
	}

	private void configurePattern(Configuration config) {
		String[] patterns = config
				.get(Configuration.REFLECTION_PARAMETER, "**").split(",");
		pattern = new LinkedList<Pattern>();
		for (String p : patterns) {
			pattern.add(config.getNewPattern(p));
		}
		pattern = Collections.unmodifiableList(pattern);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.hawk.gwt.ppc.scanner.Preprocessor#processModule(org.hawk.gwt.ppc.
	 * GWTModule)
	 */
	public void processModule(GWTModule module) throws IOException,
			ParseException {
		module.addInheritance("org.hawk.gwt.ppc.reflect.lang");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.hawk.gwt.ppc.scanner.Preprocessor#processEntryPoint(java.lang.String,
	 * java.io.Reader, java.io.StringWriter)
	 */
	public void processEntryPoint(String entryPoint, Reader reader,
			Writer writer) throws IOException {
		String pointContent = IO.toString(reader);
		int index = pointContent.lastIndexOf('}');
		if (index < 0)
			return;
		if (Log.isDebugEnabled()) {
			logReflectionClassesForThisModule(entryPoint);
		}
		writer.write(pointContent, 0, index);
		writer.write(createClassLoaderDefinition());
		writer.write(pointContent, index, pointContent.length() - index);
		metadataProcessor.clear();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.hawk.gwt.ppc.scanner.Preprocessor#acceptResource(java.lang.String)
	 */
	public boolean acceptResource(String resource) {
		for (Pattern p : pattern) {
			if (p.match(resource)) {
				return true;
			}
		}
		return false;
	}

	private String createClassLoaderDefinition() {
		final String ClassDelegate = ClassDelegate.class.getName();
		return "static {\n" + ClassDelegate + ".registerClassLoader(\n"
				+ "new " + ClassLoaderDelegate.class.getName() + "(){\n"
				+ "protected " + ClassDelegate + "<?>[] getClasses(){\n"
				+ "return new " + ClassDelegate
				+ "[]{\n"
				+ getCollectedClassesAsArrayDefinition()
				+ "};\n}\n"
				+ // end of getClasses() method
				"protected String getPackage(){\n" + "return \""
				+ getCommonPackage() + "\";"
				+ "}\n"
				+ // end of getPackage() method
				"protected Class<?>[] getNotReflectableClasses(){\n"
				+ "return new Class[]{\n"
				+ getImportedClassesAsArrayDefinition() + "};\n}\n" + // end of
																		// getNotReflectableClasses()
																		// method
				"});\n" + // end of ClassLoaderDelegate inner class
				"}\n";// end of static
	}

	private String getCollectedClassesAsArrayDefinition() {
		return toArrayDefinition(metadataProcessor.getTopClasses(),
				CLASS_DELEGATE_PROPERTY);
	}

	private String getImportedClassesAsArrayDefinition() {
		Set<String> result = new HashSet<String>();
		for (String className : metadataProcessor.getTopClasses()) {
			result.addAll(metadataProcessor.getImports(className));
		}
		result.removeAll(metadataProcessor.getTopClasses());
		return toArrayDefinition(result, CLASS_PROPERTY);
	}

	private String toArrayDefinition(Collection<String> classes, String property) {
		Iterator<String> iterator = classes.iterator();
		if (!iterator.hasNext())
			return "";
		StringBuilder result = new StringBuilder();
		appendArrayItem(property, iterator, result);
		while (iterator.hasNext()) {
			result.append(',');
			appendArrayItem(property, iterator, result);
		}
		return result.toString();
	}

	private void appendArrayItem(String property, Iterator<String> iterator,
			StringBuilder result) {
		result.append(iterator.next());
		result.append('.');
		result.append(property);
	}

	private String getCommonPackage() {
		Iterator<String> iterator = metadataProcessor.getTopClasses()
				.iterator();
		if (!iterator.hasNext())
			return "";
		String packageName = Name.getParentJavaName(iterator.next());
		while (iterator.hasNext()) {
			packageName = Name.getCommonPackage(iterator.next(), packageName);
		}
		return packageName;
	}

	private void logReflectionClassesForThisModule(String entryPoint) {
		Log.debug("\tLinking classes to entry point: " + entryPoint);
		for (String cls : metadataProcessor.getTopClasses()) {
			Log.debug("\t\tLinking " + cls);
		}
	}
}
